package org.hepeng.workx.util.proxy;


import lombok.AllArgsConstructor;
import lombok.Data;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;
import org.apache.commons.collections.CollectionUtils;
import org.hepeng.workx.util.ConstructorUtils;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;

/**
 * @author he peng
 */
public class CglibProxyFactory extends AbstractProxyFactory {

    private static final int PROXY_INVOKE_CALL_BACK_INDEX = 0;
    private static final int NATIVE_INVOKE_CALL_BACK_INDEX = 1;

    @Data
    @AllArgsConstructor
    private class CglibMethodInterceptor implements MethodInterceptor {

        private Object nativeObject;
        private List<Invoker> invokers;

        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            Invocation lastInvocation = new CglibInvocation(nativeObject , obj , method , args , proxy);
            Invocation invocation = invocationChain(invokers , -1 , lastInvocation);
            Invoker invoker = ivc -> ivc.invoke();
            return invoker.invoke(invocation);
        }
    }

    @Override
    protected Invocation nextInvocation(List<Invoker> invokers, int index, Invocation lastInvocation) {
        Invoker nextInvoker = nextInvoker(invokers, index);
        CglibInvocation cglibInvocation = (CglibInvocation) lastInvocation;
        Invocation invocation = new CglibInvocation(
                cglibInvocation.getNative() , cglibInvocation.getProxy() ,
                cglibInvocation.getMethod() , cglibInvocation.getArgs() ,
                cglibInvocation.getMethodProxy()) {
            @Override
            public Object invoke() throws Throwable {
                return nextInvoker.invoke(invocationChain(invokers , index + 1 , lastInvocation));
            }
        };
        return invocation;
    }

    @Override
    protected Object doCreateProxyInternal(Object target, Class<?> superClassOfProxy, List<Class<?>> interfaces,
                                           List<Invoker> invokers, List<InvokeFilter> filters) throws Exception {
        Enhancer enhancer = getEnhancer(superClassOfProxy, interfaces, filters);
        enhancer.setCallbacks(getCallbacks(target , invokers));
        return enhancer.create();
    }

    @Override
    protected Object doCreateProxyInternal(Class<?> superClassOfProxy, List<Class<?>> interfaces,
                                           List<Class<?>> constructorArgTypes, List<Object> constructorArgs,
                                           List<Invoker> invokers, List<InvokeFilter> filters) throws Exception {
        Enhancer enhancer = getEnhancer(superClassOfProxy, interfaces, filters);
        Object[] args = ConstructorUtils.argToArray(constructorArgs);
        Class<?>[] argTypes = ConstructorUtils.argTypeToArray(constructorArgTypes);
        Object nativeObject = org.apache.commons.lang3.reflect.ConstructorUtils.invokeConstructor(superClassOfProxy, args, argTypes);
        enhancer.setCallbacks(getCallbacks(nativeObject , invokers));
        return enhancer.create(argTypes , args);
    }

    private Callback[] getCallbacks(Object nativeObject , List<Invoker> invokers) {
        return new Callback[]{new CglibMethodInterceptor(nativeObject , invokers) , NoOp.INSTANCE};
    }

    protected Enhancer getEnhancer(Class<?> superClassOfProxy , List<Class<?>> interfaces , List<InvokeFilter> filters) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(superClassOfProxy);
        Class<?>[] ifs = new Class[interfaces.size()];
        interfaces.toArray(ifs);
        enhancer.setInterfaces(ifs);
        enhancer.setCallbackFilter(method -> {
            if (CollectionUtils.isNotEmpty(filters)) {
                boolean isProxyInvoke = true;
                for (InvokeFilter filter : filters) {
                    if (Objects.nonNull(filter)) {
                        isProxyInvoke = filter.isProxyInvoke(method);
                    }
                    if (! isProxyInvoke) {
                        return NATIVE_INVOKE_CALL_BACK_INDEX;
                    }
                }
            }
            return PROXY_INVOKE_CALL_BACK_INDEX;
        });
        return enhancer;
    }

}
